#!/usr/bin/env python3

# Golden C++ source tests for API boosting. This is effectively a test for the
# combination of api_boost.py, the Clang libtooling-based
# tools/clang_tools/api_booster, as well as the type whisperer and API type
# database.

import argparse
from collections import namedtuple
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile

import api_boost

TestCase = namedtuple('TestCase', ['name', 'description'])

# List of test in the form [(file_name, explanation)]
TESTS = list(
    map(
        lambda x: TestCase(*x), [
            ('deprecate', 'Deprecations'),
            ('elaborated_type', 'ElaboratedTypeLoc type upgrades'),
            ('using_decl', 'UsingDecl upgrades for named types'),
            ('rename', 'Annotation-based renaming'),
            ('decl_ref_expr', 'DeclRefExpr upgrades for named constants'),
            ('no_boost_file', 'API_NO_BOOST_FILE annotations'),
            ('validate', 'Validation proto header inference'),
        ]))

TESTDATA_PATH = 'tools/api_boost/testdata'


def diff(some_path, other_path):
    result = subprocess.run(['diff', '-u', some_path, other_path], capture_output=True)
    if result.returncode == 0:
        return None
    return result.stdout.decode('utf-8') + result.stderr.decode('utf-8')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Golden C++ source tests for api_boost.py')
    parser.add_argument('tests', nargs='*')
    args = parser.parse_args()

    # Accumulated error messages.
    logging.basicConfig(format='%(message)s')
    messages = []

    def should_run_test(test_name):
        return len(args.tests) == 0 or test_name in args.tests

    # Run API booster against test artifacts in a directory relative to workspace.
    # We use a temporary copy as the API booster does in-place rewriting.
    with tempfile.TemporaryDirectory(dir=pathlib.Path.cwd()) as path:
        # Setup temporary tree.
        shutil.copy(os.path.join(TESTDATA_PATH, 'BUILD'), path)
        for test in TESTS:
            if should_run_test(test.name):
                shutil.copy(os.path.join(TESTDATA_PATH, test.name + '.cc'), path)
            else:
                # Place an empty file to make Bazel happy.
                pathlib.Path(path, test.name + '.cc').write_text('')

        # Run API booster.
        relpath_to_testdata = str(pathlib.Path(path).relative_to(pathlib.Path.cwd()))
        api_boost.api_boost_tree([
            os.path.join(relpath_to_testdata, test.name)
            for test in TESTS
            if should_run_test(test.name)
        ],
                                 generate_compilation_database=True,
                                 build_api_booster=True,
                                 debug_log=True,
                                 sequential=True)

        # Validate output against golden files.
        for test in TESTS:
            if should_run_test(test.name):
                delta = diff(
                    os.path.join(TESTDATA_PATH, test.name + '.cc.gold'),
                    os.path.join(path, test.name + '.cc'))
                if delta is not None:
                    messages.append(
                        'Non-empty diff for %s (%s):\n%s\n' % (test.name, test.description, delta))

    if len(messages) > 0:
        logging.error('FAILED:\n{}'.format('\n'.join(messages)))
        sys.exit(1)
    logging.warning('PASS')
